#include "tunnelconstructor.h"
#include "notepad.h"
#include "randomstringgenerator.h"

#include <algorithm>

TunnelConstructor::TunnelConstructor()
{
    lengthVariance_.inbound = 0;
    lengthVariance_.outbound = 0;
}

bool TunnelConstructor::setName(std::string name)
{
    if (name.length() > 30)
    {
        name.erase(30);
    }

    const bool valid = std::all_of(name.begin(), name.end(), [](unsigned char c) {
        return std::isalnum(c) || c == '-' || c == '_' || c == '.';
    });

    if (!valid)
    {
        errorString_ = Notepad::SetterError::name(lang_);
        return false;
    }

    name_ = name;

    return true;
}

bool TunnelConstructor::setTunnelType(TunnelType type)
{
    tunnelType_ = type;
    return true;
}

bool TunnelConstructor::setLength(int8_t inbound, int8_t outbound)
{
    if (inbound < 0 or inbound > 8)
    {
        errorString_ = Notepad::SetterError::length(lang_);
        return false;
    }
    if (outbound < 0 or outbound > 8)
    {
        errorString_ = Notepad::SetterError::length(lang_);
        return false;
    }
    length_.inbound = inbound;
    length_.outbound = outbound;
    return true;
}

bool TunnelConstructor::setLengthVariance(int8_t inbound, int8_t outbound)
{
    if (inbound > 3 or inbound < -3)
    {
        errorString_ = Notepad::SetterError::variance(lang_);
        return false;
    }
    if (outbound > 3 or outbound < -3)
    {
        errorString_ = Notepad::SetterError::variance(lang_);
        return false;
    }
    lengthVariance_.inbound = inbound;
    lengthVariance_.outbound = outbound;
    return true;
}

bool TunnelConstructor::setQuantity(int8_t inbound, int8_t outbound)
{
    if (inbound > 16 or inbound < 1)
    {
        errorString_ = Notepad::SetterError::quantity(lang_);
        return false;
    }
    if (outbound > 16 or outbound < 1)
    {
        errorString_ = Notepad::SetterError::quantity(lang_);
        return false;
    }
    quantity_.inbound = inbound;
    quantity_.outbound = outbound;
    return true;
}

bool TunnelConstructor::setBlindedLeaseSet(bool isBlinded)
{
    blinded_ = isBlinded;
    return true;
}

bool TunnelConstructor::setTransient(bool isTransient)
{
    transient_ = isTransient;
    return true;
}

bool TunnelConstructor::setKeepAlive(int8_t interval)
{
    if (interval < 0)
    {
        interval = 0;
    }
    keepAliveInterval_ = interval;
    return true;
}

bool TunnelConstructor::setComments(bool enabled)
{
    comments_ = enabled;
    return true;
}

std::u8string TunnelConstructor::generate() const
{
    std::u8string config;
    config += u8"[" + std::u8string{ name_.begin(), name_.end() } + u8"]\n";
    if (comments_)
        config += Notepad::ConfigComment::type(lang_, tunnelType_);
    const auto typeString = tunnelTypeToString(tunnelType_);
    config += u8"type = " + std::u8string{ typeString.begin(), typeString.end() } + u8"\n";

    if (isClientType(tunnelType_))
    {
        if (comments_)
            config += Notepad::ConfigComment::clientAddress(lang_);
        config += u8"address = LOCAL_INTERFACE_ADDRESS\n";
        if (comments_)
            config += Notepad::ConfigComment::clientPort(lang_);
        config += u8"port = LOCAL_INTERFACE_PORT\n";
        if (comments_)
            config += Notepad::ConfigComment::clientDestination(lang_);
        config += u8"destination = I2P_SERVER_ADDRESS\n";
        if (comments_)
            config += Notepad::ConfigComment::clientDestinationPort(lang_);
        config += u8"destinationport = I2P_SERVER_PORT\n";
    }
    else
    {
        if (comments_)
            config += Notepad::ConfigComment::serverAddress(lang_);
        config += u8"address = LOCAL_INTERFACE_ADDRESS\n";
        if (comments_)
            config += Notepad::ConfigComment::serverHost(lang_);
        config += u8"host = IP_ADDRESS_OF_APPLICATION\n";
        if (comments_)
            config += Notepad::ConfigComment::serverPort(lang_);
        config += u8"port = APPLICATION_PORT\n";
        if (comments_)
            config += Notepad::ConfigComment::serverInport(lang_);
        config += u8"inport = I2P_SERVER_PORT\n";
    }

    if (comments_)
        config += Notepad::ConfigComment::inbound(lang_);
    if (comments_)
        config += Notepad::ConfigComment::length(lang_);
    config += u8"inbound.length = " + std::u8string( reinterpret_cast<const char8_t*>(std::to_string(length_.inbound).c_str()) ) + u8"\n";
    if (comments_)
        config += Notepad::ConfigComment::quantity(lang_);
    config += u8"inbound.quantity = " + std::u8string( reinterpret_cast<const char8_t*>(std::to_string(quantity_.inbound).c_str()) ) + u8"\n";
    if (comments_)
        config += Notepad::ConfigComment::variance(lang_);
    config += u8"inbound.lengthVariance = " + std::u8string( reinterpret_cast<const char8_t*>(std::to_string(lengthVariance_.inbound).c_str()) ) + u8"\n";
    if (comments_)
        config += Notepad::ConfigComment::outbound(lang_);
    if (comments_)
        config += Notepad::ConfigComment::length(lang_);
    config += u8"outbound.length = " + std::u8string( reinterpret_cast<const char8_t*>(std::to_string(length_.outbound).c_str()) ) + u8"\n";
    if (comments_)
        config += Notepad::ConfigComment::quantity(lang_);
    config += u8"outbound.quantity = " + std::u8string( reinterpret_cast<const char8_t*>(std::to_string(quantity_.outbound).c_str()) ) + u8"\n";
    if (comments_)
        config += Notepad::ConfigComment::variance(lang_);
    config += u8"outbound.lengthVariance = " + std::u8string( reinterpret_cast<const char8_t*>(std::to_string(lengthVariance_.outbound).c_str()) ) + u8"\n";

    if (isClientType(tunnelType_))
    {
        if (keepAliveInterval_)
        {
            if (comments_)
                config += Notepad::ConfigComment::clientKeepAlive(lang_);
            config += u8"keepaliveinterval = " + std::u8string( reinterpret_cast<const char8_t*>(std::to_string(keepAliveInterval_).c_str()) ) + u8"\n";
        }
    }
    else if (blinded_) // blinded server
    {
        if (comments_)
            config += Notepad::ConfigComment::serverBlinded(lang_);
        config += u8"signaturetype = 11\n"
                   "i2cp.leaseSetType = 5\n";
    }

    if (transient_)
    {
        if (comments_)
            config += Notepad::ConfigComment::keysTransient(lang_);
        config += u8"keys = transient-" + RandomStringGenerator::getU8string(5) + u8"\n";
    }
    else
    {
        std::string name(name_);
        std::transform(name.begin(), name.end(), name.begin(),
                       [](unsigned char c) { return std::tolower(c); });
        if (comments_)
            config += Notepad::ConfigComment::keys(lang_);
        config += u8"keys = " + std::u8string( reinterpret_cast<const char8_t*>(name.c_str()) ) + u8"-" + RandomStringGenerator::getU8string(5) + u8".dat\n";
    }

    if (comments_)
        config += Notepad::ConfigComment::footer(lang_, blinded_);

    return config;
}

std::string TunnelConstructor::tunnelTypeToString(TunnelType type)
{
    switch(type)
    {
    case TunnelType::TcpClient: {
        return "client";
    }
    case TunnelType::TcpServer: {
        return "server";
    }
    case TunnelType::UdpClient: {
        return "udpclient";
    }
    case TunnelType::UdpServer: {
        return "udpserver";
    }
    case TunnelType::HttpServer: {
        return "http";
    }
    case TunnelType::IrcServer: {
        return "irc";
    }
    case TunnelType::SocksProxy: {
        return "socks";
    }
    case TunnelType::HttpProxy: {
        return "httpproxy";
    }
    }

    return "undefined(bug)";
}

TunnelType TunnelConstructor::stringToTunnelType(const std::string &type)
{
    if (type == "client")
    {
        return TunnelType::TcpClient;
    }
    else if (type == "server")
    {
        return TunnelType::TcpServer;
    }
    else if (type == "udpclient")
    {
        return TunnelType::UdpClient;
    }
    else if (type == "udpserver")
    {
        return TunnelType::UdpServer;
    }
    else if (type == "http")
    {
        return TunnelType::HttpServer;
    }
    else if (type == "irc")
    {
        return TunnelType::IrcServer;
    }
    else if (type == "socks")
    {
        return TunnelType::SocksProxy;
    }
    else if (type == "httpproxy")
    {
        return TunnelType::HttpProxy;
    }
    return TunnelType::TcpClient;
}

bool TunnelConstructor::isClientType(TunnelType type)
{
    switch(type)
    {
    case TunnelType::TcpClient: {
        return true;
    }
    case TunnelType::UdpClient: {
        return true;
    }
    default : {
        return false;
    }
    }
}
